# coding=utf-8
import csv
from math import ceil
import pandas as pd
import numpy as np
import pdb

UTILIZATION_THRESHOLD_LOCAL = 0.4
UTILIZATION_THRESHOLD_GLOBAL = 0.3
COST = [1, 2, 3, 10]

class Node():
    def __init__(self, id):
        self.id = str(id)
        self.out_edge = []

    def set_field(self, field):
        self.field = field
    
    def set_portNum(self, port_num):
        self.port_num = port_num

    def add_outEdge(self, link):
        self.out_edge.append(link)

class Link():
    def __init__(self, id, src_id, dst_id):
        self.id = str(id)
        self.src_id = str(src_id)
        self.dst_id = str(dst_id)

    def set_bandwidth(self, bandwidth):
        self.bandwidth = bandwidth

class Graph():
    def __init__(self):
        self.nodes = {}
        self.links = {}

    def get_node(self, id):
        return self.nodes[id]
    
    def get_link(self, id):
        return self.links[id]
    
    def add_nodes(self, nodes_dataFrame):
        for i in range(nodes_dataFrame.shape[0]):
            id, field_str, port_num = nodes_dataFrame.iloc[i]
            node = Node(id)
            field = []
            for s in ('接入', '汇聚', '核心'):
                if (s in field_str):
                    field.append(s)
            
            node.set_field(field)
            node.set_portNum(port_num)
            if (id in self.nodes):
                raise Exception("节点重复")
            else:
                self.nodes[id] = node

    def add_links(self, links_dataFrame):
        for i in range(links_dataFrame.shape[0]):
            id, src, dst, bandwidth = links_dataFrame.iloc[i]
            id = str(id)
            link = Link(id, src, dst)
            link.set_bandwidth(bandwidth)
            if (id in self.links):
                raise Exception("链路重复")
            else:
                self.links[id] = link
            self.nodes[src].add_outEdge(link)

            reverse_id = id + "_rev"
            link = Link(reverse_id, dst, src)
            link.set_bandwidth(bandwidth)
            if (reverse_id in self.links):
                raise Exception("链路重复")
            else:
                self.links[reverse_id] = link
            self.nodes[dst].add_outEdge(link)


class Scoring_tools():
    def __init__(self, node_file, link_file, traffic_file, new_node_file, new_link_file, path_file):
        self._traffic = pd.read_csv(traffic_file)
        self._paths = self.set_path(path_file)
        graph = Graph()
        self.set_graph(graph, node_file, link_file)
        self.set_graph(graph, new_node_file, new_link_file)
        self._graph = graph

        self._newNodes = pd.read_csv(new_node_file)
        self._newLinks = pd.read_csv(new_link_file)

    def set_path(self, filename):
        data = pd.read_csv(filename)
        s = data.shape[0]
        return {data.iloc[i,0]: str(data.iloc[i, -1]).split("-->") for i in range(s)} 

    def set_graph(self, graph, node_file, link_file):
        nodes_dataFrame = pd.read_csv(node_file)
        links_dataFrame = pd.read_csv(link_file)
        graph.add_nodes(nodes_dataFrame)
        graph.add_links(links_dataFrame)

    def check_newNodes(self):
        graph = self._graph
        for i in range(self._newNodes.shape[0]):
            id, _, port_num = self._newNodes.iloc[i]
            id = str(id)
            if (port_num > 10):
                raise Exception("扩容节点端口数错误")
            node = graph.get_node(id)
            node.port_num = 10
            if (node.field == []):
                raise Exception("扩容节点域错误")
            elif ('接入' in node.field and '核心' in node.field):
                raise Exception("扩容节点域错误")

    def check_newLinks(self):
        links_dataFrame = self._newLinks
        graph = self._graph
        bandwidth_candidate = [10]
        for i in range(links_dataFrame.shape[0]):
            id, src_id, dst_id, bandwidth = links_dataFrame.iloc[i]
            id = str(id)
            src_id = str(src_id)
            dst_id = str(dst_id)
            src = graph.get_node(src_id)
            dst = graph.get_node(dst_id)
            comon_field = list(set(src.field) & (set(dst.field)))
            if (comon_field == []):
                raise Exception("扩容链路域错误")
            for s in comon_field:
                if (s == '核心'):
                    bandwidth_candidate.append(100)
                    break
                if (s == '接入'):
                    bandwidth_candidate.append(1)
                    if (len(comon_field) == 2):
                        break
                    success = False
                    for edge in src.out_edge:
                        if (edge.id != id and edge.dst_id == dst_id):
                            success = True
                            break
                    if (not success):
                        raise Exception("接入层链路扩容错误")
            if not (bandwidth in bandwidth_candidate):
                raise Exception("链路带宽设置错误")
            else:
                src.port_num = src.port_num - 1
                dst.port_num = dst.port_num - 1
                if (src.port_num < 0 or dst.port_num < 0):
                    raise Exception("扩容节点剩余端口数错误")

    def check_paths(self):
        if (self._traffic.shape[0] != len(self._paths)):
            raise Exception("路径数量不符")
        for i in range(self._traffic.shape[0]):
            id, src_id, dst_id, _ = self._traffic.iloc[i]
            src_id = str(src_id)
            dst_id = str(dst_id)
            edges = self._paths[id]
            id = str(id)
            for edge_id in edges:
                link = self._graph.get_link(edge_id)
                if (src_id != link.src_id):
                    raise Exception("路径"+id+"错误")
                src_id = link.dst_id
            if (src_id != dst_id):
                raise Exception("路径"+id+"错误")

    def check_utilization(self):
        links = self._graph.links
        traffic_link = {id: 0 for id in links}
        for i in range(self._traffic.shape[0]):
            id, _, _, traffic = self._traffic.iloc[i]
            edges = self._paths[id]
            for edge_id in edges:
                if ("_rev" in edge_id):
                    traffic_link[edge_id] += traffic
                    continue
                traffic_link[edge_id + "_rev"] += traffic
        bandwidth_link = {link.id: link.bandwidth for link in links.values()}
        total_traffic = 0
        total_bandwidth = 0
        max_utilization = 0
        for id in links:
            traffic = traffic_link[id]
            bandwidth = bandwidth_link[id]
            utilization = traffic/bandwidth
            if (max_utilization < utilization):
                max_utilization = utilization
                # print (max_utilization)
            # 获取链路带宽利用率最大值

            total_traffic += traffic
            total_bandwidth += bandwidth
        # 获取全局带宽利用率
        global_utilization = total_traffic/total_bandwidth*2
        return (max_utilization, global_utilization)

    def cal_cost(self):
        cost = self._newNodes.shape[0]*COST[3]
        for bandwidth in self._newLinks["带宽"]:
            index = int(np.log10(bandwidth))
            cost += COST[index]
        return cost
    
    def convert_utilization2level(self, utilization):
        # level  utilization
        # 1      u <= 0.4
        # 2      0.4 < u <= 0.5
        # 3      0.5 < u <= 0.6
        # 4      0.6 < u <= 0.7
        # ...
        if (utilization <= 0.4):
            return 1
        else:
            return ceil(utilization*10 - 3)

    def cal_score(self):
        self.check_newNodes()
        self.check_newLinks()
        self.check_paths()
        max_utilization, global_utilization = self.check_utilization()
        level = self.convert_utilization2level(max_utilization)
        cost = self.cal_cost()
        return [level, cost, global_utilization]

def get_score(folder_name, traffic_filename):
    node_file = folder_name + "node.csv"
    link_file = folder_name + "link.csv"
    traffic_file = folder_name + traffic_filename
    new_node_file = folder_name + "new_node.csv"
    new_link_file = folder_name + "new_link.csv"
    path_file = folder_name + "path.csv"
    tool = Scoring_tools(node_file, link_file, traffic_file, new_node_file, new_link_file, path_file)
    score = tool.cal_score()
    score.insert(0, traffic_file)
    with open(r'./score.csv', 'a') as csv_file:
        csv.writer(csv_file).writerow(score)
    return score

